﻿//     Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;
using Microsoft.Gateway.Configuration;
using Microsoft.Gateway.RulesConfigurator;
using Microsoft.Gateway.Utils;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace RulesConfigurator.UnitTests
{
    [TestClass]
    [DeploymentItem("web.config")]
    public class AuthorizationConfiguratorTests
    {
        private string mOriginalWebConfig = "web.config";
        [TestInitialize]
        public void PrepareTestFolder()
        {
        }
        [TestMethod]
        public void SingleRoleWithRestrictedPathWithAuth()
        {
            string testConfigFile = string.Format("{0}.{1}", mOriginalWebConfig, Guid.NewGuid().ToString("N"));
            File.Copy(mOriginalWebConfig, testConfigFile);
            RulesConfiguration config = new RulesConfiguration() { ApplicationName = "fake name", Directory = "fake dictionary" };
            config.Roles.Add("testrole", 
                            new Role{ 
                                RequireAuthenticationBelow = new string[]{"securedpathwithstrangename"
                            }});

            AuthorizationConfigurator.ApplyConfiguration(config, testConfigFile);

            XDocument webConfig = XDocument.Load(testConfigFile);
            XElement element = null;

            Assert.IsTrue(HasExactlyOneElement(webConfig,
                                    "//configuration/location[@path='testrole/securedpathwithstrangename']/system.web/authorization/deny", out element),
                                    "There should be only one 'deny' element for location 'testrole/securedpathwithstrangename'.");
            Assert.IsTrue(HasExactlyOneElement(webConfig,
                                    "//configuration/system.web/authorization/allow[@users='*']", out element),
                                    "There should be only one 'authorization' element that allows all anonymous users.");
        }
        [TestMethod]
        public void MultipleRoleWithOneRestrictedPathWithAuth()
        {
            string testConfigFile = string.Format("{0}.{1}", mOriginalWebConfig, Guid.NewGuid().ToString("N"));
            File.Copy(mOriginalWebConfig, testConfigFile);
            RulesConfiguration config = new RulesConfiguration() { ApplicationName = "fake name", Directory = "fake dictionary" };
            config.Roles.Add("testrole",
                            new Role
                            {
                                RequireAuthenticationBelow = new string[]{"securedpathwithstrangename"
                            }
                            });
            config.Roles.Add("testrole2", new Role());

            AuthorizationConfigurator.ApplyConfiguration(config, testConfigFile);

            XDocument webConfig = XDocument.Load(testConfigFile);
            XElement element = null;

            Assert.IsTrue(HasExactlyOneElement(webConfig,
                                    "//configuration/location[@path='testrole/securedpathwithstrangename']/system.web/authorization/deny", out element),
                                    "There should be only one 'deny' element for location 'testrole/securedpathwithstrangename'.");
            Assert.IsFalse(HasExactlyOneElement(webConfig,
                                   "//configuration/location[@path='testrole2']/system.web/authorization/deny", out element),
                                   "There should NOT be a 'deny' element for location 'testrole2'.");
            Assert.IsTrue(HasExactlyOneElement(webConfig,
                                    "//configuration/system.web/authorization/allow[@users='*']", out element),
                                    "There should be only one 'authorization' element that allows all anonymous users.");
        }

        [TestMethod]
        public void SingleRoleWithRestrictedPathWithoutAuth()
        {
            string testConfigFile = string.Format("{0}.{1}", mOriginalWebConfig, Guid.NewGuid().ToString("N"));
            File.Copy(mOriginalWebConfig, testConfigFile);
            RulesConfiguration config = new RulesConfiguration() { ApplicationName = null, Directory = "fake dictionary" };
            config.Roles.Add("testrole",
                            new Role
                            {
                                RequireAuthenticationBelow = new string[]{"securedpathwithstrangename"
                            }
                            });
            config.Roles.Add("testrole2", new Role());

            AuthorizationConfigurator.ApplyConfiguration(config, testConfigFile);

            XDocument webConfig = XDocument.Load(testConfigFile);
            XElement element = null;

            var denies = webConfig.XPathSelectElements("//configuration/location/system.web/authorization/deny");
            Assert.AreEqual(0, denies.Count(), "There should not be any 'deny' elements.");
            Assert.IsTrue(HasExactlyOneElement(webConfig,
                                    "//configuration/system.web/authorization/allow[@users='*']", out element),
                                    "There should be only one 'authorization' element that allows all anonymous users.");
        }

        [TestMethod]
        public void SingleRoleWithoutRestrictedPathWithAuth()
        {
            string testConfigFile = string.Format("{0}.{1}", mOriginalWebConfig, Guid.NewGuid().ToString("N"));
            File.Copy(mOriginalWebConfig, testConfigFile);
            RulesConfiguration config = new RulesConfiguration() { ApplicationName = "fake application", Directory = "fake dictionary" };
            config.Roles.Add("testrole", new Role());

            AuthorizationConfigurator.ApplyConfiguration(config, testConfigFile);

            XDocument webConfig = XDocument.Load(testConfigFile);
            XElement element = null;

            var denies = webConfig.XPathSelectElements("//configuration/location/system.web/authorization/deny");
            Assert.AreEqual(0, denies.Count(), "There should not be any 'deny' elements.");
            Assert.IsTrue(HasExactlyOneElement(webConfig,
                                    "//configuration/system.web/authorization/deny[@users='?']", out element),
                                    "There should be only one 'authorization' element that denies all anonymous users.");
        }

        [TestMethod]
        public void VerifyAuthModulesEnabledForConfiguredAuth()
        {
            string testConfigFile = string.Format("{0}.{1}", mOriginalWebConfig, Guid.NewGuid().ToString("N"));
            File.Copy(mOriginalWebConfig, testConfigFile);

            Assert.IsTrue(DirectoryConfigurator.ApplyConfiguration("gateway.onmicrosoft.com", "fake dictionary", testConfigFile) == ConfiguratorResults.Success, 
                "DirectoryConfigurator.ApplyConfiguration() failed to return successful return code.");

            XDocument webConfig = XDocument.Load(testConfigFile);
            XElement element = null;

            var modules = webConfig.XPathSelectElement("//configuration/system.webServer/modules");
            var moduleAdds = modules.Elements(XNamespace.None + "add");
            Assert.IsTrue(moduleAdds.Count() >= 2, "There should be at least 2 'module/add' elements.");
            Assert.IsTrue(HasExactlyOneElement(webConfig,
                                    "//configuration/system.webServer/modules/add[@name = 'GatewayFAM']", out element),
                                    "There should be only one GatewayFAM module present.");
            Assert.IsTrue(HasExactlyOneElement(webConfig,
                                    "//configuration/system.webServer/modules/add[@type='GatewayFAM.GWFederationAuthenticationModule, GatewayFAM']", out element),
                                    "There should be only one module of GatewayFAM type present.");
            Assert.IsTrue(HasExactlyOneElement(webConfig,
                                    "//configuration/system.webServer/modules/add[@name = 'SessionAuthenticationModule']", out element),
                                    "There should be only one SessionAuthenticationModule module present.");
            Assert.IsTrue(HasExactlyOneElement(webConfig,
                                    "//configuration/system.webServer/modules/add[@type='System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089']", out element),
                                    "There should be only one module of SessionAuthentionModule type present.");
        }

        [TestMethod]
        public void VerifyAuthModulesDisabledForNoAuth()
        {
            string testConfigFile = string.Format("{0}.{1}", mOriginalWebConfig, Guid.NewGuid().ToString("N"));
            File.Copy(mOriginalWebConfig, testConfigFile);

            Assert.IsTrue(DirectoryConfigurator.ApplyConfiguration("", "", testConfigFile) == ConfiguratorResults.Success,
                "DirectoryConfigurator.ApplyConfiguration() failed to return successful return code.");

            XDocument webConfig = XDocument.Load(testConfigFile);

            Assert.IsTrue(ElementCount(webConfig, "//configuration/system.webServer/modules/add[@name = 'GatewayFAM']") == 0,
                                    "There should not be any GatewayFAM modules present.");
            Assert.IsTrue(ElementCount(webConfig, "//configuration/system.webServer/modules/add[@type='GatewayFAM.GWFederationAuthenticationModule, GatewayFAM']") == 0,
                                    "There should not be any modules of GatewayFAM type present.");
            Assert.IsTrue(ElementCount(webConfig, "//configuration/system.webServer/modules/add[@name = 'SessionAuthenticationModule']") == 0,
                                    "There should not be any SessionAuthenticationModule module present.");
            Assert.IsTrue(ElementCount(webConfig, 
                                    "//configuration/system.webServer/modules/add[@type='System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089']") == 0,
                                    "There should be any modules of SessionAuthentionModule type present.");
        }

        [TestMethod]
        public void VerifyIssuerNameRegistry()
        {
            string testConfigFile = string.Format("{0}.{1}", mOriginalWebConfig, Guid.NewGuid().ToString("N"));
            File.Copy(mOriginalWebConfig, testConfigFile);

            Assert.IsTrue(DirectoryConfigurator.ApplyConfiguration("gateway.onmicrosoft.com", "fake dictionary", testConfigFile) == ConfiguratorResults.Success,
                "DirectoryConfigurator.ApplyConfiguration() failed to return successful return code.");

            XDocument webConfig = XDocument.Load(testConfigFile);

            var issuerNameRegistry = webConfig.XPathSelectElement("//configuration/system.identityModel/identityConfiguration/issuerNameRegistry");
            var authority = issuerNameRegistry.Element(XNamespace.None + "authority");
            Assert.AreEqual(authority.Attribute(XNamespace.None + "name").Value,
                "https://sts.windows.net/0be2ee01-5b59-41c0-896e-cc87c828d0c7/",
                "IssuerNameRegistry authority does not represent gateway.onmicrosoft.com");
            var keys = authority.Element(XNamespace.None + "keys")
                .Elements(XNamespace.None + "add");
            Assert.IsTrue(keys.Count() >= 1, "There should be at least 1 'keys/add' elements.");
        }

        [TestMethod]
        public void VerifyMooncakeAuth()
        {
            string testConfigFile = string.Format("{0}.{1}", mOriginalWebConfig, Guid.NewGuid().ToString("N"));
            File.Copy(mOriginalWebConfig, testConfigFile);
            AzureEnvironment.InitializeEnvironment(AzureEnvironments.AzureChinaCloud);

            Assert.IsTrue(DirectoryConfigurator.ApplyConfiguration("gatewayservice.partner.onmschina.cn", "fake dictionary", testConfigFile) == ConfiguratorResults.Success,
                "DirectoryConfigurator.ApplyConfiguration() failed to return successful return code.");

            XDocument webConfig = XDocument.Load(testConfigFile);

            var issuerNameRegistry = webConfig.XPathSelectElement("//configuration/system.identityModel/identityConfiguration/issuerNameRegistry");
            var authority = issuerNameRegistry.Element(XNamespace.None + "authority");
            Assert.AreEqual(authority.Attribute(XNamespace.None + "name").Value,
                "https://sts.chinacloudapi.cn/7b0e6239-e8c9-40d5-bd21-eb3d6c2eaf12/",
                "IssuerNameRegistry authority does not represent gatewayservice.partner.onmschina.cn");
            var keys = authority.Element(XNamespace.None + "keys")
                .Elements(XNamespace.None + "add");
            Assert.IsTrue(keys.Count() >= 1, "There should be at least 1 'keys/add' elements.");
            var wsFedElement = webConfig.XPathSelectElement("//configuration/system.identityModel.services/federationConfiguration/wsFederation");
            Assert.AreEqual(wsFedElement.Attribute(XNamespace.None + "issuer").Value,
                "https://login.chinacloudapi.cn/gatewayservice.partner.onmschina.cn/wsfed",
                "WSFederation issuer does not represent gatewayservice.partner.onmschina.cn");

            AzureEnvironment.InitializeEnvironment(AzureEnvironments.AzureCloud);
        }

        [TestCleanup]
        public void Cleanup()
        {
        }

        private bool HasExactlyOneElement(XDocument document, string xPath, out XElement element)
        {
            element = null;
            var elements = document.XPathSelectElements(xPath);
            if (elements != null && elements.Count() == 1)
            {
                element = elements.First();
                return true;
            }
            return false;
        }

        private int ElementCount(XDocument document, string xPath)
        {
            var elements = document.XPathSelectElements(xPath);
            if (elements != null)
            {
                return elements.Count();
            }
            return 0;
        }
    }
}
